Skip to content

[improve][misc] Migrate Swagger annotations to OpenAPI 3 (Swagger Core v3, jakarta) - #25937

Merged
lhotari merged 5 commits into
apache:masterfrom
lhotari:lh-improve-openapi-v3-migration
Jun 5, 2026
Merged

[improve][misc] Migrate Swagger annotations to OpenAPI 3 (Swagger Core v3, jakarta)#25937
lhotari merged 5 commits into
apache:masterfrom
lhotari:lh-improve-openapi-v3-migration

Conversation

@lhotari

@lhotari lhotari commented Jun 4, 2026

Copy link
Copy Markdown
Member

Fixes #18947

PIP: PIP-472 (follow-up: completes the Swagger migration that PIP-472 explicitly deferred)

Motivation

PIP-472 migrated Pulsar from javax.* to jakarta.* but deferred the Swagger migration ("Swagger 1.x -> Swagger Core 2.x is deferred to a follow-up (decoupled, compile-/doc-only)"). Pulsar still depended on Swagger 1.6.2 (io.swagger:*), which is end-of-life, javax-era, and incompatible with the jakarta REST tier. In addition, OpenAPI document generation (the swagger.json files published as the REST API reference on pulsar.apache.org) had no equivalent in the Gradle build after the Maven removal.

This PR migrates all Swagger annotations to Swagger Core v3 (io.swagger.core.v3:swagger-annotations-jakarta:2.2.50, OpenAPI 3) and restores spec generation via the official io.swagger.core.v3.swagger-gradle-plugin.

Modifications

Annotation migration (61 Java files)

  • @Api -> @Tag (+@Hidden where v1 had hidden=true), @ApiOperation -> @Operation, @ApiResponse(code=, message=, response=, responseContainer=) -> @ApiResponse(responseCode=, description=, content=) with @ArraySchema/additionalPropertiesSchema container mappings, @ApiParam -> @Parameter (bound params incl. @FormDataParam multipart parts) / @RequestBody (body params), @ApiModel/@ApiModelProperty -> @Schema with requiredMode, @Example/@ExampleProperty -> @ExampleObject.
  • Map-valued responses use @Schema(type = "object", additionalPropertiesSchema = X.class) — the only form the swagger-core 2.2.50 resolver renders correctly; the @Content-level additionalPropertiesSchema = @Schema(implementation = X.class) form silently emits an empty value schema.
  • Protobuf/lightproto types in @Schema(implementation=) replaced with type = "object" schemas (Jackson introspection crashes on them; those endpoints emit protobuf JSON).
  • BaseGenerateDocumentation reflection ported to @Schema; removed the unused NoSwaggerDocumentation marker and stale workaround comments for swagger-core#449 / swagger-ui#558 (both fixed upstream); nested map schemas are now expressed precisely.
  • Fixed malformed example JSON payloads and cleaned up typos/whitespace/grammar in annotation descriptions (separate commits).

Resolution of #18947 ("Unstable swagger output") — ExtPersistentTopics/ExtNonPersistentTopics removed

  • The PartitionedTopicMetadata variant of createPartitionedTopic shares PUT /{tenant}/{namespace}/{topic}/partitions with the int variant; the OpenAPI spec forbids two operations on the same path+method, which made the Swagger 1.x output unstable — so the methods had been exiled into separate, undocumented Ext*Topics workaround classes whose implementations then drifted.
  • At runtime Jersey disambiguates by content type (@Consumes("application/vnd.partitioned-topic-metadata+json")), and OpenAPI 3 expresses exactly that: one operation whose request body carries one schema per media type. The visible createPartitionedTopic operation now documents both bodies (application/json -> integer, application/vnd.partitioned-topic-metadata+json -> PartitionedTopicMetadata with partitions and properties).
  • The metadata overload moved into PersistentTopics (marked @Operation(hidden = true) so the path is documented once); both overloads share a new validateAndCreatePartitionedTopic helper, which NonPersistentTopics overrides with its looser validation — the inherited metadata overload picks it up via virtual dispatch. The Ext*Topics classes are deleted (net -159 lines).

Build

  • Version catalog: swagger = 2.2.50, swagger-annotations -> io.swagger.core.v3:swagger-annotations-jakarta; removed the unused swagger-core alias.
  • New io.swagger.core.v3.swagger-gradle-plugin (2.2.50) configuration in pulsar-broker replicating the Maven swagger profile from branch-4.2: 7 resolve tasks with the same output file names and base info/servers, assembled by ./gradlew :pulsar-broker:generateOpenApiSpecs into build/openapi/ with the flat + v2/ + v3/ layout published on pulsar.apache.org. The plugin's default javax resolver dependencies are replaced with swagger-jaxrs2-jakarta via the swaggerDeps configuration.
  • pulsar-docs-tools, pulsar-websocket and pulsar-client-tools now declare guava/commons-lang3 explicitly (previously leaked onto the compile classpath via swagger-core 1.x transitives).
  • io.kubernetes:client-java's transitive Swagger 1.x annotations are excluded: they are inert metadata on the generated k8s models (verified — no Methodref/Fieldref to io.swagger in any class of either jar; the JVM ignores missing annotation types during reflection). The k8s-exercising test suites pass with the exclusion.
  • Shade include pattern updated to the io.swagger.core.v3 group; LICENSE.bin.txt updated for both distributions.

Verifying this change

  • Make sure that the change passes the CI checks.

This change is already covered by existing tests and verified as follows:

  • The generated OpenAPI documents are operation-identical with the published 4.2.1 REST API docs: 597/597 operations across all 7 documents, zero drift; map-valued responses render the same additionalProperties value schemas as 4.2.1.
  • Every annotated REST resource class in the repo (broker, functions worker, websocket, proxy) was run through the real swagger-core resolver to prove it scans cleanly (@Hidden classes excluded by design, matching 1.x behavior).
  • checkBinaryLicense passes for the server and shell distributions; full compileTestJava, checkstyle, spotless and pulsar-docs-tools tests pass; the kubernetes-client-dependent test suites (pulsar-functions-runtime, pulsar-functions-secrets, pulsar-broker-auth-oidc) pass with the swagger-annotations exclusion.
  • For the Unstable swagger output #18947 fix: PersistentTopicsTest passes (46/46, including the metadata-variant creation tests, now routed through PersistentTopics); two consecutive OpenAPI generation runs produce byte-identical output, confirming the reported instability is gone.

Does this pull request potentially affect one of the following parts:

If the box was checked, please highlight the changes

  • Dependencies (add or upgrade a dependency)
  • The public API
  • The schema
  • The default values of configurations
  • The threading model
  • The binary protocol
  • The REST endpoints
  • The admin CLI options
  • The metrics
  • Anything that affects deployment

Dependency changes: io.swagger:swagger-annotations/swagger-core 1.6.2 removed; io.swagger.core.v3:swagger-annotations-jakarta 2.2.50 added (annotations only, no transitive deps); io.swagger:swagger-annotations excluded from io.kubernetes:client-java. REST endpoint behavior is unchanged — only the documentation annotations on the resource classes changed. As noted in PIP-472's compatibility section, authors of plugins that contribute JAX-RS resources using Swagger 1.x annotations need to migrate to io.swagger.v3.oas.annotations.* when recompiling against this version.

Assisted-by: Claude Code (Opus 4.8)

lhotari added 4 commits June 4, 2026 22:05
…e v3, jakarta)

Completes the Swagger migration deferred from PIP-472: Swagger 1.6.2
(io.swagger, javax-era, EOL) is replaced with Swagger Core v3
io.swagger.core.v3:swagger-annotations-jakarta:2.2.50 across the whole
codebase, and OpenAPI document generation is restored in the Gradle build.

Annotations (61 Java files):
- @Api -> @tag (+@hidden where v1 had hidden=true), @apioperation ->
  @operation, @apiresponse(code=, message=, response=, responseContainer=)
  -> @apiresponse(responseCode=, description=, content=) with
  @ArraySchema / additionalPropertiesSchema container mappings,
  @ApiParam -> @parameter (bound params incl. @FormDataParam multipart
  parts) / @RequestBody (body params), @ApiModel/@ApiModelProperty ->
  @Schema with requiredMode, @Example/@ExampleProperty -> @ExampleObject.
- Map-valued responses use @Schema(type = "object",
  additionalPropertiesSchema = X.class) - the only form the swagger-core
  2.2.50 resolver actually renders; the @Content-level
  additionalPropertiesSchema = @Schema(implementation = X.class) form
  silently emits additionalProperties: {}.
- Protobuf/lightproto types in @Schema(implementation=) are replaced with
  type = "object" schemas (Jackson introspection crashes on them and the
  endpoints emit protobuf JSON anyway).
- BaseGenerateDocumentation reflection ported to @Schema
  (generateDocByApiModelProperty -> generateDocBySchema and friends).
- Removed the unused NoSwaggerDocumentation marker annotation and stale
  workaround comments for swagger-api/swagger-core#449 and
  swagger-ui#558 (both fixed upstream years ago); nested map schemas are
  now expressed precisely.
- Fixed malformed example JSON payloads that degraded to plain strings in
  generated docs (trailing comma, stray '+', missing comma).

Build:
- Version catalog: swagger = 2.2.50, swagger-annotations ->
  io.swagger.core.v3:swagger-annotations-jakarta; swagger-core alias
  removed (no code uses swagger-core classes).
- New io.swagger.core.v3.swagger-gradle-plugin (2.2.50) configuration in
  pulsar-broker replicating the Maven build's `swagger` profile from
  branch-4.2: 7 ResolveTask instances with the same output file names,
  base info/servers from src/main/openapi/*.json, assembled by
  ./gradlew :pulsar-broker:swaggerDocs into build/docs with the
  flat + v2/ + v3/ layout published on pulsar.apache.org. The plugin's
  default javax resolver dependencies are overridden with
  swagger-jaxrs2-jakarta on the swaggerDeps configuration.
- pulsar-docs-tools, pulsar-websocket and pulsar-client-tools now declare
  guava/commons-lang3 explicitly (previously leaked onto the compile
  classpath via swagger-core 1.x transitives).
- io.kubernetes:client-java's transitive Swagger 1.x annotations are
  excluded everywhere: they are inert metadata on the generated k8s
  models (verified: no Methodref/Fieldref in any class, runtime jar has
  zero references) and the JVM ignores missing annotation types.
- Shade include pattern updated to the io.swagger.core.v3 group;
  LICENSE.bin.txt entries updated (checkBinaryLicense passes for server
  and shell distributions).

Verification: generated documents are operation-identical with the
published 4.2.1 docs (597/597 operations across all 7 files, zero drift);
every annotated resource class in the repo (broker, functions worker,
websocket, proxy) resolves cleanly through the real scanner; map-valued
responses render the same additionalProperties value schemas as 4.2.1.

Assisted-by: Claude Code (Opus 4.8)
…ptions

Proofreads the summary/description strings of the OpenAPI (Swagger v3)
annotations and fixes only clear textual defects, preserving meaning:

- Spelling typos: "doesn't exit" -> "doesn't exist" (PersistentTopics,
  Namespaces, rest Topics), "ect" -> "etc", "sre" -> "are",
  "serviceconfiguration" -> "ServiceConfiguration", "Requested" ->
  "Request" (worker stats), "BrokersBase admin apis" -> "Brokers admin
  apis" (leaked base-class name).
- Missing spaces at string-concatenation boundaries, e.g.
  "at thenamespace level", "tenant orsubscriber", "cluster.If
  authorization", "thiscall", "will betrimmed", "C++'s[Boost]".
- Stray leading spaces in operation summaries (" Set retention...") and
  doubled spaces mid-sentence.
- Missing closing parentheses, e.g. "(if instance-id is not provided,
  the stats of all instances is returned" and the sink/source
  parallelism and schema-type descriptions.

Awkward-but-correct grammar was intentionally left untouched. Generated
documents remain operation-identical with the published 4.2.1 docs
(597/597 operations).

Assisted-by: Claude Code (Opus 4.8)
Renames the aggregate OpenAPI documentation task in pulsar-broker from
`swaggerDocs` to `generateOpenApiSpecs` and its output directory from
build/docs to build/openapi:

  ./gradlew :pulsar-broker:generateOpenApiSpecs   -> pulsar-broker/build/openapi/

The generated file names and the flat + v2/ + v3/ layout are unchanged.

Assisted-by: Claude Code (Opus 4.8)
Fixes ungrammatical and garbled wording in the OpenAPI annotation
summary/description strings while preserving the technical meaning,
e.g.:

- "Get is enable sub type for specified topic" -> "Get the enabled
  subscription types for the specified topic" (and the Set variant)
- "will let the broker removes all producers" -> "will make the broker
  remove all producers"
- "Topic don't owner by this broker\!" -> "Topic is not owned by this
  broker\!"; "Broker don't use MLTransactionMetadataStore\!" -> "Broker
  doesn't use ..."; "This Broker is not enable transaction" -> "This
  Broker does not have transactions enabled"
- "Partitioned topic already exist" -> "already exists"; "Expiry
  messages" -> "Expire messages"; "An REST endpoint" -> "A REST
  endpoint"; "The type of an value" -> "the type of a value";
  "configurations's name" -> "configurations' names"
- "Requested should be executed by Monitoring agent" -> "The request
  should be executed by the Monitoring agent"
- copy-paste leaks: Source endpoints saying "Pulsar Function
  successfully created/updated" / "The function was successfully
  deleted" now say "Pulsar Source"; setEntryFilters request body
  description said "Enable sub types for the specified topic"
- '"advertisedListeners" must enabled in broker side' -> 'must be
  enabled on the broker side'; missing spaces after punctuation
  ("schema.if", "**DISCARD**:silently", "earlier(no later)")

Candidates were located by spell-checking the descriptions rendered
into the generated OpenAPI documents and from the previous cleanup
pass's report. Generated documents remain operation-identical with the
published 4.2.1 docs (597/597 operations).

Assisted-by: Claude Code (Opus 4.8)
…pache#18947

ExtPersistentTopics/ExtNonPersistentTopics were a workaround
(self-described as such in their javadoc) for
apache#18947: the
PartitionedTopicMetadata variant of createPartitionedTopic shares
PUT /{tenant}/{namespace}/{topic}/partitions with the int variant, the
OpenAPI specification forbids two operations on the same path and
method, and the Swagger 1.x toolchain produced unstable output - so the
methods were exiled into separate undocumented classes, whose
implementations then drifted from the originals.

At runtime Jersey disambiguates the two methods by content type (the
metadata variant declares
@consumes("application/vnd.partitioned-topic-metadata+json")), and
OpenAPI 3 expresses exactly that: a single operation whose requestBody
carries one schema per media type. With that representation the
workaround classes are unnecessary:

- The metadata overload moves into PersistentTopics next to the int
  overload, marked @operation(hidden = true) so the path is documented
  once; the visible operation's request body documents both content
  types (application/json -> integer,
  application/vnd.partitioned-topic-metadata+json ->
  PartitionedTopicMetadata with partitions and properties).
- Both overloads share a new validateAndCreatePartitionedTopic helper.
  NonPersistentTopics overrides only this helper (non-persistent topics
  validate the topic name without the partitioned-name/policy checks),
  so the inherited metadata overload picks up the right validation via
  virtual dispatch and the duplicated, drifted method bodies are gone.
- ExtPersistentTopics and ExtNonPersistentTopics are removed;
  PersistentTopicsTest now exercises the metadata variant through
  PersistentTopics. The /admin/v2 Jersey registration is package-based,
  so no registration changes are needed.

Verified: PersistentTopicsTest passes (46/46, including the
metadata-variant creation tests); two consecutive OpenAPI generation
runs are byte-identical (the instability from apache#18947 is gone); the
generated document remains operation-identical with the published 4.2.1
docs (521/521 admin v2 operations) while PUT .../partitions now
documents both content types.

Assisted-by: Claude Code (Opus 4.8)
@lhotari
lhotari merged commit 9624715 into apache:master Jun 5, 2026
44 checks passed
@lhotari lhotari added this to the 5.0.0-M1 milestone Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unstable swagger output

3 participants